import {get,set,has} from "lodash-es"
import {Temp} from "./temp"


const DfaState = {
    Initial: Symbol("Initial"),
    Int: Symbol("Int"),
    Id: Symbol("Id"),
    GT: Symbol("GT"),
    GE: Symbol("GE"),
    Assignment: Symbol("Assignment"),
    Plus: Symbol("Plus"),
    Minus: Symbol("Minus"),
    Star: Symbol("Star"),
    Slash: Symbol("Slash"),
    SemiColon: Symbol("SemiColon"),
    LeftParen: Symbol("LeftParen"),
    RightParen: Symbol("RightParen"),

    IntLiteral: Symbol("IntLiteral"),
    //
    LeftBlock1: Symbol("LeftBlock1"),
    LeftBlock2: Symbol("LeftBlock2"),
    LeftBlock3: Symbol("LeftBlock3"),
    RightBlock1: Symbol("RightBlock1"),
    RightBlock2: Symbol("RightBlock2"),
    RightBlock3: Symbol("RightBlock3"),
    LeftArray: Symbol("LeftArray"),
    RightArray: Symbol("RightArray"),
    Block: Symbol("Block"),
    Array: Symbol("Array"),
    LeftExp:Symbol("LeftExp"),
    RightExp:Symbol("RightExp"),
    Exp: Symbol("Exp"),
    Split: Symbol("Split"),
    String: Symbol("String"),
    Null: Symbol("Null"),
    Comma: Symbol("Comma"),
}

const TokenType = {
    Identifier: Symbol("Identifier"),
    IntLiteral: Symbol("IntLiteral"),
    GT: Symbol("GT"),
    GE: Symbol("GE"),
    Plus: Symbol("Plus"),
    Minus: Symbol("Minus"),
    Star: Symbol("Star"),
    Slash: Symbol("Slash"),
    SemiColon: Symbol("SemiColon"),
    LeftParen: Symbol("LeftParen"),
    RightParen: Symbol("RightParen"),
    Assignment: Symbol("Assignment"),
    
    Int: Symbol("Int"),
    //
    LeftBlock: Symbol("LeftBlock"),
    RightBlock: Symbol("RightBlock"),
    LeftArray: Symbol("LeftArray"),
    RightArray: Symbol("RightArray"),
    Block: Symbol("Block"),
    Array: Symbol("Array"),
    Key: Symbol("Key"),
    Val: Symbol("Val"),
    LeftExp:Symbol("LeftExp"),
    RightExp:Symbol("RightExp"),
    Exp: Symbol("Exp"),
    Split: Symbol("Split"),
    String: Symbol("String"),
    Comma: Symbol("Comma"),
};

// 单个token对象
class SimpleToken {
    constructor() {
        this.type = null;
        this.text = null;
    }
    type:any
    text:any
    getType() {
        return this.type;
    }

    getText() {
        return this.text;
    }
}

class TokenReader {
    constructor(tokens:string[]) {
        this.tokens = tokens;
        this.pos = 0;
        console.log('.....',tokens)
    }
    pos:number
    tokens:string[]

    read() {
        if (this.pos < this.tokens.length) {
            return this.tokens[this.pos++];
        }
        return null;
    }

    peek() {
        if (this.pos < this.tokens.length) {
            return this.tokens[this.pos]
        }
        return null;
    }

    unread() {
        if (this.pos > 0) {
            this.pos--;
        }
    }

    getPosition() {
        return this.pos;
    }

    setPosition(position:number) {
        if (position >= 0 && position < this.tokens.length) {
            this.pos = position;
        }
    }

}

export function dump(tokenReader:TokenReader){

    let token = null;
    while((token = tokenReader.read())){
        console.log(token)
    }
}


const getValue = (str:string,type:any,context:any) => {
    let res = str;
    switch(type){
        case TokenType.Identifier:
            if(has(context,str)){
                res = get(context,str)
            }    
        break;
        case TokenType.String:
            res = Temp.exp2str(str,context)    
        break;
        case TokenType.Exp:
            res = Temp.exec(str,context)
        break;
    }

    return res;
}

export class Latex {
    constructor() {
        this.tokenText = null;
        this.tokens = [];
        this.token = null;
    }

    tokens:string[]
    token:any
    tokenText:any

    stringSign:string = ''

    appendTokenText(ch:string) {
        this.tokenText = this.tokenText + ch;
    }
    // 是否是字母
    isIdentifier(ch:string) {
        return /[\$\w\.]/.test(ch)
        // return (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
    }
    // 是否是数字
    // isDigit(ch:string) {
    //     return ch >= "0" && ch <= "9";
    // }
    // 是否是空白符
    // isBlank(ch:string) {
    //     return ch === " " || ch === "\t" || ch === "\n";
    // }

    resetToken(v?:string){
        if(this.token.type){
            this.token.text = (v || this.tokenText).trim();
            // console.log('resetToken ....',this.token,this.tokenText,v)
            this.tokens.push(this.token);
            this.tokenText = "";
            this.token = new SimpleToken();
        }
    }
    /**
     * 有限状态机进入初始状态。
     * 这个初始状态其实并不做停留，它马上进入其他状态。
     * 开始解析的时候，进入初始状态；某个Token解析完毕，也进入初始状态，在这里把Token记下来，然后建立一个新的Token。
     * @param ch
     * @return
     */
    initToken(ch:string,bool=[TokenType.Exp,TokenType.String].includes(this.token.type)) {
        if (this.tokenText.length > 0 && !bool) {
            this.token.text = this.tokenText.trim();
            if(this.token.type){
                this.tokens.push(this.token);
            }
            this.tokenText = "";
            this.token = new SimpleToken();
        }

        let newState = DfaState.Initial;
        if (this.isIdentifier(ch)) {
            //进入Id状态
            newState = DfaState.Id;
            //第一个字符是字母
            // if (ch === "l") {
            //     // newState = DfaState.Id_let1;
            // } else {
            //     //进入Id状态
            //     newState = DfaState.Id;
            // }
            if(!this.token.type){
                this.token.type = TokenType.Identifier
            }
            // this.token.type = TokenType.Identifier;
        } 
        // else if (this.isDigit(ch)) {
        //     //第一个字符是数字
        //     newState = DfaState.LetLiteral;
        //     this.token.type = TokenType.LetLiteral;
        // } 
        // else if (ch === ">") {
        //     //第一个字符是>
        //     newState = DfaState.GT;
        //     this.token.type = TokenType.GT;
        // } else if (ch === "+") {
        //     newState = DfaState.Plus;
        //     this.token.type = TokenType.Plus;
        // } else if (ch === "-") {
        //     newState = DfaState.Minus;
        //     this.token.type = TokenType.Minus;
        // } else if (ch === "*") {
        //     newState = DfaState.Star;
        //     this.token.type = TokenType.Star;
        // } else if (ch === "/") {
        //     newState = DfaState.Slash;
        //     this.token.type = TokenType.Slash;
        // } else if (ch === ";") {
        //     newState = DfaState.SemiColon;
        //     this.token.type = TokenType.SemiColon;
        // } 
        else if (ch === "(") {
            newState = DfaState.LeftParen;
            if(!this.token.type){
                this.token.type = TokenType.LeftParen
            }
        } 
        else if (ch === ")") {
            newState = DfaState.RightParen;
            if(!this.token.type){
                this.token.type = TokenType.RightParen
            }
        }
        else if(ch === ','){
            newState = DfaState.Comma;
            if(!this.token.type){
                this.token.type = TokenType.Comma
            }
        }
        else if (ch === "=") {
            newState = DfaState.Assignment;
            if(!this.token.type){
                this.token.type = TokenType.Assignment
            }
        }
        else if(ch == '[') {
            newState = DfaState.LeftArray;
            if(!this.token.type){
                this.token.type = TokenType.LeftArray
            }
            // this.token.type = TokenType.LeftArray
        }
        else if(ch == ']'){
            newState = DfaState.RightArray
            if(!this.token.type){
                this.token.type = TokenType.RightArray
            }
            // this.token.type = TokenType.RightArray
        }
        else if(ch == '{'){
            newState = DfaState.LeftBlock1
            if(!this.token.type){
                this.token.type = TokenType.LeftBlock
            }
            // this.token.type = TokenType.LeftBlock
        }
        else if(ch == '}'){
            newState = DfaState.RightBlock1
            if(!this.token.type){
                this.token.type = TokenType.RightBlock
            }
            // this.token.type = TokenType.RightBlock
        }
        else if(ch == '"' || ch == "'" || ch == '`'){
            newState = DfaState.String;
            this.stringSign = ch;
            // this.token.type = TokenType.String;
            if(!this.token.type){
                this.token.type = TokenType.String
            }
        }
        else if(ch == ':'){
            newState = DfaState.Split
            if(!this.token.type){
                this.token.type = TokenType.Split
            }
            // this.token.type = TokenType.Split
        }
        
        this.appendTokenText(ch);
        return newState;
    }

    getTokens(code:string) {
        console.log(String.raw `${code}`)
        return String.raw `${code}`.split('')
    }

    tokenize(code:string) {
        this.tokens = [];
        const reader = new TokenReader(this.getTokens(code))
        this.tokenText = "";
        this.token = new SimpleToken();
        let chI:any = 0;
        let ch:any = 0;
        let pch:any = 0
        let state = DfaState.Initial;
        try {
            while ((chI = reader.read())) {
                // console.log(pch,'....',ch,state,this.token.type)
                pch = ch;
                ch = chI;
                // console.log(ch:string);
                switch (state) {
                    case DfaState.Initial:
                        state = this.initToken(ch);
                        break;
                    case DfaState.Id:
                        if (this.isIdentifier(ch)) {
                            this.appendTokenText(ch);
                        } else {
                            state = this.initToken(ch);
                        }
                        break;
                    case DfaState.GT:
                        if (ch === '=') {
                            this.token.type = TokenType.GE;
                            state = DfaState.GE;
                            this.appendTokenText(ch)
                        } else {
                            state = this.initToken(ch)
                        }
                        break;
                    case DfaState.LeftParen:
                    case DfaState.RightParen:
                    case DfaState.Comma:
                    case DfaState.Assignment:
                    case DfaState.LeftArray:
                    case DfaState.RightArray:
                    case DfaState.Split:
                    case DfaState.Null:
                        state = this.initToken(ch);          //退出当前状态，并保存Token
                        break;
                    case DfaState.LeftBlock1:
                        if(ch == '{'){
                            // this.token.type = TokenType.Exp;
                            state = DfaState.LeftBlock2;
                            // this.appendTokenText(ch)
                        }
                        else{
                            state = this.initToken(ch); 
                        }
                        break;
                    case DfaState.LeftBlock2:
                        if(ch == '{'){
                            state = DfaState.LeftBlock3;
                        }else{
                            // this.token.type = TokenType.LeftExp;
                            // this.resetToken('{{');
                            this.tokenText = '';
                            this.token.type = TokenType.Exp
                            state = this.initToken(ch);
                            // this.appendTokenText(ch)
                        }
                        break;
                    case DfaState.LeftBlock3:
                        this.token.type = TokenType.LeftBlock;
                        this.resetToken();
                        // this.token.type = TokenType.LeftExp;
                        // this.resetToken('{{');
                        this.token.type = TokenType.Exp;
                        state = this.initToken(ch);                          
                        break;
                    case DfaState.String:
                        this.appendTokenText(ch)
                        if(pch != '\\' && ch == this.stringSign){
                            if(this.token.type == TokenType.String){
                                this.tokenText = this.tokenText.slice(1,-1)
                                this.resetToken();
                            }
                            state = DfaState.Initial; 
                            this.stringSign = '';
                        }
                        // console.log(this.stringSign,'====',ch,state)
                        break;
                    case DfaState.RightBlock1:
                        if(ch == '}' && this.token.type == TokenType.Exp){
                            state = DfaState.RightBlock2
                        }else{
                            state = this.initToken(ch); 
                        }
                        break;
                    case DfaState.RightBlock2:
                        this.tokenText = this.tokenText.slice(0,-1);
                        this.resetToken();
                        // this.token.type = TokenType.RightExp;
                        // this.resetToken('}}');
                        state = this.initToken(ch);
                        break;
                    default:
                        break;
                }
            }
            // 把最后一个token送进去
            if (this.tokenText.length > 0) {
                this.initToken(ch)
            }

        } catch (error) {
            console.log(error)
        }

        return new  TokenReader(this.tokens)
    }

    parse(code:string,context:any){
        const tr = this.tokenize(code);
        const stack = [];
        let current:any;
        let token:any
        let isObj = false;
        let pt:any;
        let kt:any;
        let vt:any;
        let dt:any;

        let key = '';
        let val = '';
        let dv = '';

        const reset = () => {
            // console.log('key = ',key,'','val = ',val,'','dv = ',dv)
            if(key){
                const k = getValue(key,kt,context)
                const td = dv ? getValue(dv,dt,context) : '';
                if(isObj){
                    const tv = val ? getValue(val,vt,context) : ''
                    
                    const v = tv || td;
                    if(v){
                        set(current,k || key,v)
                    }
                    else{
                        set(current,key,k)
                    }
                }else{
                    current.push(k || td)
                }
                
            }
            val = dv = key = vt = dt = kt = ''    
        }

        while((token = tr.read())){
            // console.log('....',token)
            const {type,text} = token as SimpleToken;
            
            switch(type){
                case TokenType.LeftBlock:
                    if(isObj && current){
                        // current[key] = {};
                        const k = getValue(key,kt,context) || key
                        set(current,k,{})
                        // current = current[key]
                        current = get(current,k)
                    }
                    else if(current){
                        current.push({})
                        current = current.at(-1)
                    }
                    else{
                        current = {} 
                    }
                    stack.push(current)
                    isObj = true; 
                break;
                case TokenType.LeftArray:
                    if(isObj && current){
                        // current[key] = [];
                        const k = getValue(key,kt,context) || key
                        set(current,k,[])
                        // current = current[key]
                        current = get(current,k)
                    } 
                    else if(current){
                        current.push([])
                        current = current.at(-1)
                    }
                    else {
                        current = []
                    }
                    stack.push(current)
                    isObj = false; 
                break;
                case TokenType.RightArray:
                case TokenType.RightBlock:
                    reset();   
                    if(stack.length > 1){
                        stack.pop();
                        current = stack.at(-1);
                        isObj = !Array.isArray(current) 
                    }else{
                        current = stack.pop()
                    }
                    
                break;
                // case TokenType.Assignment:break;
                case TokenType.Comma:
                    reset();   
                break;
                case TokenType.Identifier:
                case TokenType.String:
                case TokenType.Exp: 
                    if(pt == TokenType.Split){
                        val = text;
                        vt = type;
                    }
                    else if(pt == TokenType.Assignment){
                        dv = text;
                        dt = type;
                    }
                    else {
                        key = text;
                        kt = type;
                    }
                    // console.log(pt,type,'key = ',key,'','val = ',val,'','dv = ',dv)
                break;
                default:break;
            }

            pt = type;
        }

        return current
    }
}